package vista.objetos;

import java.awt.event.MouseAdapter;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.swing.JFrame;

import controlador.ContenedorDeErrores;
import controlador.ControladorEditor;

import eventos.abrir.EventoEditarVentanaEntidad;

import vista.Tema;
import vista.treeDiagrama.NodoEntidad;
import vista.treeDiagrama.NodoTreeDiagrama;

import logica.Atributo;
import logica.Entidad;
import logica.IdExternoInexistenteException;
import logica.Identificador;
import logica.ObjetoDiagrama;

import logica.persistencia.EntidadGraficablePersistente;
import logica.persistencia.GraficableConEtiquetaPersistente;
import memento.IMemento;
import memento.MementoCompuesto;
import memento.MementoEntidadGraficable;

public class EntidadGraficable extends GraficableConEtiquetaConAtributos {
	
	private EstrategiaEntidadGraficable tipoEntidad;
	private Entidad entidadDecorada;
	private List<IdentificadorGraficable> identificadores = new LinkedList<IdentificadorGraficable>();

	public EntidadGraficable(Entidad entidad, EstrategiaEntidadGraficable tipo)
	{
		super();
		
		this.entidadDecorada = entidad;
		this.entidadDecorada.addObserver(this);
		this.setTipoEntidad(tipo);
		this.entidadDecorada.getGraficables().add(this);
		
		inicializarEtiqueta();
	}
	
	private void inicializarEtiqueta()
	{
		getEtiqueta().setText(entidadDecorada.getNombre());
		getEtiqueta().setSize(Tema.getInstancia().getEntidadAnchoMitad() * 2, Tema.getInstancia().getEntidadAltoMitad() * 2);
	}

	public void dibujarse(Object areaDeDibujo)
	{
		GLAutoDrawable gLDrawable = (GLAutoDrawable) areaDeDibujo; 
		GL2 gl = gLDrawable.getGL().getGL2();
		
		this.getTipoEntidad().dibujarse(gl, this);
		
		for(IdentificadorGraficable identificador: this.getIdentificadores())
			identificador.dibujarse(areaDeDibujo);
	}
	
	@Override
	public String toString() {
		return this.entidadDecorada.getNombre();
	}
	
	@Override
	public ObjetoDiagrama getObjetoDiagramaDecorado(){
		return this.entidadDecorada;
	}
	
	@Override
	public NodoTreeDiagrama getNodoTreeDiagrama() {
		return new NodoEntidad(this);
	}

	public EstrategiaEntidadGraficable getTipoEntidad() {
		return tipoEntidad;
	}

	public void setTipoEntidad(EstrategiaEntidadGraficable tipoEntidad) {
		this.tipoEntidad = tipoEntidad;
	}

	@Override
	public void update(Observable o, Object arg) {
		getEtiqueta().setText(this.entidadDecorada.getNombre());	
	}
	
	@Override
	public boolean esElGraficableDe(ObjetoDiagrama objeto) {
		return entidadDecorada.equals(objeto);
	}
		
	@Override
	/*
	 * Verifica que la entidad graficable a copiar no se encuentre en el contenedor. Si se encuentra retorna
	 * esa referencia, sino crea una copia.
	 * 
	 * La copia es de:
	 * 		- los datos gráfico correspondientes a la entidad
	 * 		- los atributos de la entidad
	 * 		- Si es una relación débil:
	 * 			- las referencias externas
	 * 			- las relaciones que vincula la entidad con la referencia externa
	 * 		- creación de los identificadores.
	 */
	public GraficableConEtiqueta copiarse(IContenedorDeGraficable contenedor)
	{
		if(!entidadDecorada.soyEntidadDebil())
			return copiarEntidadFuerte(contenedor);

		return copiarEntidadDebil(contenedor);
	}
	
	private EntidadGraficable copiarEntidadDebil(IContenedorDeGraficable contenedor)
	{
		EntidadGraficable entidadCopia =  this.copiarDatos(contenedor);

		entidadCopia.setReferenciasExternas(this.copiarReferenciasExternas(contenedor));
		
		try {
			entidadCopia.crearIdentificadores();
		} catch (IdExternoInexistenteException e) { 
			e.printStackTrace();
		}
		
		return entidadCopia;
	}
	
	private EntidadGraficable copiarEntidadFuerte(IContenedorDeGraficable contenedor)
	{
		EntidadGraficable entidadCopia =  this.copiarDatos(contenedor);
		try {
			entidadCopia.crearIdentificadores();
		} catch (IdExternoInexistenteException e) { // no debería pasar, sino mal programado
			e.printStackTrace();
		}
		
		return entidadCopia;
	}

	/* copia la relación que vincula la entidad débil con la fuerte. */
	private List<EntidadRolGraficable> copiarReferenciasExternas(IContenedorDeGraficable contenedor) 
	{
		List<EntidadRolGraficable> entidadesCopia = new LinkedList<>();
		RelacionGraficable relacionOriginal, relacionCopia;
		
		for(EntidadRolGraficable entidadRol: this.getReferenciasExternas())
		{
			relacionOriginal = this.getDiagrama().getRelacionFuerteDebil((Entidad)entidadRol.getEntidad().getObjetoDiagramaDecorado(), this.entidadDecorada);
			relacionCopia = (RelacionGraficable) relacionOriginal.copiarse(contenedor);
			entidadesCopia.add(relacionCopia.getEntidadRol((Entidad)entidadRol.getEntidad().getObjetoDiagramaDecorado()));
		}
		
		return entidadesCopia;
	}

	private EntidadGraficable copiarDatos(IContenedorDeGraficable contenedor)
	{
		for(GraficableConEtiqueta graficable: contenedor.getGraficables())
			if (graficable.esElGraficableDe(entidadDecorada))
				return (EntidadGraficable)graficable;

		//=> la entidad aún no existe
		EntidadGraficable entidadCopia =  new EntidadGraficable(entidadDecorada, getTipoEntidad());
		entidadCopia.getPunto().setLocation(this.getPunto());
		entidadCopia.getEtiqueta().setLocation(this.getEtiqueta().getLocation());
		
		try {
			entidadCopia.setAtributos(copiarAtributos(entidadCopia.getPunto()));
		} catch (AtributoTipoNoSoportadoException | GraficableException e) {
			e.printStackTrace();
		}
		
		contenedor.agregarGraficable(entidadCopia);
		return entidadCopia;
	}
	
	@Override
	public MouseAdapter getEventoEditarGraficable(JFrame ventanaPadre) {
		return new EventoEditarVentanaEntidad(ventanaPadre, ControladorEditor.getInstancia(), this);
	}

	@Override
	public GraficableConEtiquetaPersistente getGraficableConEtiquetaPersistente() {
		return new EntidadGraficablePersistente(this);
	}

	@Override
	public  IMemento getMementoGenerico() { 
		
		if (getObjetoDiagramaDecorado().getGraficables().size() > 1)
			return new MementoCompuesto(this);
		
		return this.getMementoEspecializado();
	}
	
	@Override
	public IMemento getMementoEspecializado() {
		return new MementoEntidadGraficable(this);
	}
		
	@Override
	/*
	 * 	Si la entidad es una entidad débil, borro primero todas las relaciones
	 * q correspondan a esa entidad.
	 * 
	 * 	Si la entidad no está como id externo de ninguna entidad se puede borrar.
	 * 
	 *	Si la entidad está en una jerarquía:
	 *		y es la entidad padre. 
	 *			Se borra la jerarquía y la entidad.
	 *		y es una entidad hija y es la única.
	 *			Se borra la jerarquía y la entidad.
	 *		y es una de las tantas hijas
	 *			Se borra la entidad no más.
	 * 
	 *	Si la entidad está en una relación 
	 *		terciaria o de más elementos
	 *			Se borra la entidad
	 *		binaria
	 *			Se borra la entidad y la relación.
	 *
	 *	Si la entidad está como id externo de otra:
	 *		se borra la entidad débil primero.
	 *		luego, se borra la entidad
	 */
	public void borrarseDelDiagrama()
	{
		if(this.getDiagrama() == null)
			return;
	
		/*Esto es porque sino me salta java.util.ConcurrentModificationException
		 * porque estoy borrando elementos del diagrama mientras lo estoy recorriendo.*/
		int posicion;
		GraficableConEtiqueta graficable;
		EntidadGraficable entidad;
		
		if (this.soyUnaEntidadGraficableDebil())
			this.borrarEntidadDebil();
		
		for (GraficableConEtiqueta entidadesGraficables: entidadDecorada.getGraficables())
		{
			posicion = 0;
			entidad = (EntidadGraficable) entidadesGraficables;
			
			while(posicion < entidadesGraficables.getDiagrama().getGraficables().size())
			{
				graficable = entidadesGraficables.getDiagrama().getGraficables().get(posicion);
				posicion += borrarElGraficable(graficable, entidad);
			}
		}
		
		try {
			this.getDiagrama().borrarGraficable(this);
		} catch (NoEsObjetoDiagramableException e) {//no debería pasar.
			e.printStackTrace();
		}
	}

	/* 1 avanzo al siguiente graficable
	 * 0 me quedo en la misma posición
	 */
	private int borrarElGraficable(GraficableConEtiqueta graficable, EntidadGraficable entidad) 
	{
		if (!graficable.tieneLaEntidad(entidad)) //si no tiene la entidad no hay que borrar al graficable
			return 1;
		
		//=> la tiene
		
		if (graficable instanceof EntidadGraficable) //=> es id externo del graficable => hay q borra la entidad débil
		{ 
			graficable.borrarseDelDiagrama();
			return 0;
		}
			
		//=> es una relación o jerarquía
		if(graficable.sePuedeBorrar(entidad)) //=> relación binaria o la entidad es el padre o el único hijo de la jerarquía
		{
			graficable.borrarseDelDiagrama();
			return 0;
		}
		
		//=> es una relación ternaria o más elementos o es una jerarquía y la entidad es una de los tantos hijos que tiene
		//=> implica no se tiene que borrar el graficable, pero hay que sacar la referencia esa entidad.
		try {
			graficable.eliminarEntidad(entidad);
		} 
		catch (GraficableException e) {
			e.printStackTrace();
		}
		return 1;
	}
	
	private void borrarEntidadDebil()
	{
		RelacionGraficable relacion;
		Entidad fuerte, debil;
		try { 
			for (GraficableConEtiqueta entidadesGraficables: entidadDecorada.getGraficables())
			{
				for (EntidadRolGraficable entidad: ((EntidadGraficable)entidadesGraficables).getReferenciasExternas())
				{
					fuerte = (Entidad) entidad.getEntidad().getObjetoDiagramaDecorado();
					debil = (Entidad) entidadesGraficables.getObjetoDiagramaDecorado();
					
					relacion = entidadesGraficables.getDiagrama().getRelacionFuerteDebil(fuerte, debil);
					if (relacion != null)
						relacion.borrarRelacionDebil();
				}
			} 
		}
		catch (NoEsObjetoDiagramableException e) { // no debería pasar
			e.printStackTrace();
		}
		
	}

	public boolean soyUnaEntidadGraficableDebil() {
		return this.entidadDecorada.soyEntidadDebil();
	}

	@Override
	/*
	 * retorna true si la tiene como referencia externa
	 */
	public boolean tieneLaEntidad(EntidadGraficable entidadGraficable)
	{
		for (EntidadRolGraficable entidadG: this.getReferenciasExternas())
			if (entidadG.getEntidad() == entidadGraficable)
				return true;

		return false;
	}

	@Override
	/* una entidad no borra otra entidad */
	public boolean sePuedeBorrar(EntidadGraficable entidadGraficable) {
		return false;
	}
	
	public void setReferenciasExternas(DiagramaGraficable diagrama) throws IdExternoInexistenteException 
	{
		for(Entidad entidadFuerte: entidadDecorada.getReferenciaAEntidadesExternas())
			this.getReferenciasExternas().add(diagrama.getEntidadRolFuerteDebil(entidadFuerte, entidadDecorada));
	}

	public void verificarSiReferenciasExternasValidas() throws IdExternoInexistenteException 
	{
		DiagramaGraficable diagrama = this.getDiagrama();
		
		for(Entidad entidadFuerte: entidadDecorada.getReferenciaAEntidadesExternas())
			diagrama.getEntidadRolFuerteDebil(entidadFuerte, entidadDecorada);
	}
	
	public List<IdentificadorGraficable> getIdentificadores() {
		return identificadores;
	}
	
	public void crearIdentificadores() throws IdExternoInexistenteException
	{
		List<Identificable> grafIdent;
		identificadores = new LinkedList<IdentificadorGraficable>();
		 
		for (Identificador identificador: entidadDecorada.getIdentificadores())
		{
			grafIdent = new LinkedList<Identificable>();

			for (Atributo atributoI: identificador.getAtributos())
				buscarAtributoGraficable(grafIdent, atributoI);
			
			for (Entidad entidad: identificador.getEntidades())
				buscarEntidadGraficable(grafIdent, entidad);
			
			this.getIdentificadores().add(new IdentificadorGraficable(grafIdent));
		}
	}

	public void verificarIdentificadores() throws IdExternoInexistenteException
	{
		List<Identificable> grafIdent;
		
		for (Identificador identificador: entidadDecorada.getIdentificadores())
		{
			grafIdent = new LinkedList<Identificable>();

			for (Atributo atributoI: identificador.getAtributos())
				buscarAtributoGraficable(grafIdent, atributoI);
			
			for (Entidad entidad: identificador.getEntidades())
				buscarEntidadGraficable(grafIdent, entidad);
		}
	}
	
	private void buscarEntidadGraficable(List<Identificable> grafIdent, Entidad entidad) throws IdExternoInexistenteException 
	{
		for (EntidadRolGraficable entidadG: this.getReferenciasExternas())
		{
			if (entidadG.getEntidad().esElGraficableDe(entidad))
			{
				grafIdent.add(entidadG);
				return;
			}
		}
		
		throw new IdExternoInexistenteException("No existe una relación con la entidad graficable: <"+ entidad + "> para poder ser usada como identificador.");
	}
	
	private void buscarAtributoGraficable(List<Identificable> grafIdent, Atributo atributo) throws IdExternoInexistenteException 
	{
		for (AtributoGraficable atributoG: this.getAtributos())
		{
			if (atributoG.esElGraficableDe(atributo))
			{
				grafIdent.add(atributoG);
				return;
			}
		}
		
		throw new IdExternoInexistenteException("No existe el atributo graficable correspondiente a: " + atributo);
	}

	@Override
	public void validar(ContenedorDeErrores errores) 
	{
		super.validar(errores);
		
		if (entidadDecorada.getAtributos().size() < 1 )
			errores.agregarError("Diagrama graficable: <<" +  this.getDiagrama().getDiagrama().getNombre() + ">> - La entidad graficable <<" + entidadDecorada.getNombre() + ">> no tiene atributos.");
	}

}